﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.ComponentModel;
using ClientNodeLib.ServerNodeLib;
using System.Diagnostics;

namespace ClientNodeLib
{
	/// <summary>
	/// This class manages communication with the scheduler server
	/// by queueing up messages and attempting to deliver repeatedly.
	/// 
	/// It enables the scenario of an unreachable server when a job
	/// concludes.
	/// </summary>
	public class ResultCommunicationDispatcher
	{
		protected static String defaultFile = "commqueue.xml";
		protected static TimeSpan watchDuration = TimeSpan.FromSeconds(10);

		protected List<ResultQueueItem> queue;
		System.Threading.Thread queueWatcher;

		public ResultCommunicationDispatcher()
		{
			if (System.IO.Directory.Exists(ClientNodeSettings.Default.CommQueueCopyDir) == false)
			{
				System.IO.Directory.CreateDirectory(ClientNodeSettings.Default.CommQueueCopyDir);
			}

			queue = new List<ResultQueueItem>();

			LoadQueue();
			queueWatcher = new System.Threading.Thread(this.watchQueue);
		}

		~ResultCommunicationDispatcher()
		{
			Stop();

			SaveQueue();
		}

		/// <summary>
		/// Deserializes the queue from the xml file in the current directory
		/// </summary>
		public void LoadQueue()
		{
			try
			{
				CommonLib.Tracer.StartActivity("Load Comm Queue");
				lock (queue)
				{
					if (System.IO.File.Exists(defaultFile))
					{
						using (System.IO.StreamReader reader = new System.IO.StreamReader(defaultFile))
						{
							System.Xml.Serialization.XmlSerializer s = new System.Xml.Serialization.XmlSerializer(typeof(List<ResultQueueItem>));
							queue = (List<ResultQueueItem>)s.Deserialize(reader);
							reader.Close();
						}
						CommonLib.Tracer.ReportInformation("Successfully loaded comm queue file");
					}
					else
					{
						CommonLib.Tracer.ReportInformation("Comm queue file did not exist");
					}
				}
			}
			catch (Exception ex)
			{
				CommonLib.Tracer.ReportError("General exception", ex);
			}
			finally
			{
				if (queue == null)
					queue = new List<ResultQueueItem>();
				CommonLib.Tracer.EndActivity("Load Comm Queue");
			}
		}

		/// <summary>
		/// Serializes the queue to the xml file in the current directory
		/// </summary>
		public void SaveQueue()
		{
			try
			{
				CommonLib.Tracer.StartActivity("Save Queue");
				using (System.IO.StreamWriter writer = new System.IO.StreamWriter(defaultFile))
				{
					lock (queue)
					{
						System.Xml.Serialization.XmlSerializer s = new System.Xml.Serialization.XmlSerializer(typeof(List<ResultQueueItem>));
						s.Serialize(writer, queue);
						writer.Close();
						CommonLib.Tracer.ReportInformation("Comm queue saved successfully");
					}
				}
			}
			catch (Exception ex)
			{
				CommonLib.Tracer.ReportError("Error saving comm queue", ex);
			}
			finally
			{
				CommonLib.Tracer.EndActivity("Save Queue");
			}
		}

		/// <summary>
		/// Returns the number of items in the queue.
		/// Please note that some items may have been sent.
		/// This is because the queue only removes items
		/// upon seeing they've been sent, not immediately afterward
		/// </summary>
		/// <returns>The number of items in the queue</returns>
		public int QueueSize()
		{
			lock (queue)
			{
				return queue.Count;
			}
		}

		/// <summary>
		/// Stops the queue (the background thread)
		/// </summary>
		public void Stop()
		{
			if (queueWatcher != null)
				queueWatcher.Abort();
		}

		/// <summary>
		/// Starts the queue (the background thread)
		/// </summary>
		public void Start()
		{
			queueWatcher.Start();
		}

		/// <summary>
		/// Background thread method to watch the queue
		/// If a new item appears, the comm queue tries to send
		/// the result and its data to the server
		/// 
		/// Only upon a successful result is the item removed from the queue
		/// </summary>
		protected void watchQueue()
		{
			SchedulerServerClient schedulerServer = null;
			while (true)
			{
				if (queue == null)
					break;

				lock (queue)
				{
					try
					{
						CommonLib.Tracer.StartActivity("Watch Queue");
						schedulerServer = SimpleSchedulerClient.BuildWcfClient();
						if (schedulerServer == null)
							break;

						foreach (ResultQueueItem qi in queue)
						{
							try
							{
								if (qi.IsSent == false)
								{
									CommonLib.Tracer.ReportInformation("Trying to send queue item BatchId " + qi.BatchId);
									bool allFilesSent = true;
									for (int filei = 0; filei < qi.Files.Count; filei++)
									{
										String file = qi.Files[filei];
										CommonLib.Tracer.ReportInformation("Trying to send file " + file);
										String clientPath = qi.ClientPaths[filei];
										byte[] data = new byte[0];
										if (System.IO.File.Exists(file))
										{
											using (System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.Open))
											{
												int offset = 0;
												int chunkNum = 0;
												int errorNum = 0;
												while (offset <= fs.Length)
												{
													int transferLength = (int)Math.Min(fs.Length - offset, qi.ChunkSize);
													if (transferLength < 0)
													{
														errorNum++;
													}
													else
													{
														data = new byte[transferLength];
														fs.Read(data, 0, data.Length);
														if (schedulerServer.PostResourceChunk(qi.BatchId, clientPath, chunkNum, data))
														{
															offset += transferLength;
															if (transferLength == 0)
																offset++; // account for sending the empty file
															chunkNum++;
														}
														else
														{
															CommonLib.Tracer.ReportWarning("Failed to post resource chunk of " + qi.BatchId + " to " + clientPath + " of chunknum " + chunkNum + " out of " + data.Length);
															errorNum++;
														}
													}

													if (errorNum > 25)
													{
														CommonLib.Tracer.ReportWarning("Could not send file " + file + " due to error limit reach");
														allFilesSent = false;
														break;
													}
												}
												fs.Close();
											}
										}
									}
									Guid resultGuid = schedulerServer.PostResult(qi.BatchId, qi.ResultInfo);
									CommonLib.Tracer.ReportInformation("Send result " + resultGuid + " to server");
									//serverNode.PostResult(qi.BatchId, qi.IsSuccess, qi.Message, qi.Duration);
									if (allFilesSent && resultGuid.Equals(Guid.Empty) == false)
									{
										CommonLib.Tracer.ReportInformation("All files sent and post result succeeded");
										qi.IsSent = true;
									}
									else
									{
										CommonLib.Tracer.ReportInformation("Failed to either send all files or post result failed");
									}

								}
							}
							catch (System.Threading.ThreadAbortException)
							{
								CommonLib.Tracer.ReportError("Thread abort received");
								return;
							}
							catch (Exception ex)
							{
								CommonLib.Tracer.ReportError("General Exception for Queue Item", ex);
							}
						}
					}
					catch (Exception ex)
					{
						CommonLib.Tracer.ReportError("General Exception Watch Queue", ex);
					}
					finally
					{
						if (schedulerServer != null)
							schedulerServer.Close();
					}
				}// lock(queue)
				CommonLib.Tracer.EndActivity("Watch Queue");
				System.Threading.Thread.Sleep(watchDuration);
			}
		}

		/// <summary>
		/// Adds the following result and corresponding return resources to the queue
		/// The method copies them from their original locations to a specific directory.
		/// The background thread then picks up the updated Queue and actually sends them
		/// on to the server
		/// </summary>
		/// <param name="info">The ResultInfo object with summary information</param>
		/// <param name="returnResources">The list of return resources the server wants to see</param>
		public void EnqueueMessage(ResultInfo info, List<ResourceInfo> returnResources)
		{
			CommonLib.Tracer.StartActivity("EnqueueMessage");
			try
			{
				ResultQueueItem qi = new ResultQueueItem();
				qi.BatchId = info.BatchId;
				qi.ResultInfo = info;
				CommonLib.Tracer.ReportInformation("Enqueueing item " + qi.BatchId + " with result " + qi.ResultInfo);

				String outDir = System.IO.Path.Combine(ClientNodeSettings.Default.CommQueueCopyDir, info.BatchId.ToString());
				if (System.IO.Directory.Exists(outDir) == false)
					System.IO.Directory.CreateDirectory(outDir);

				qi.Files = new List<string>();
				foreach (ResourceInfo resInfo in returnResources)
				{
					String file = resInfo.DestName;
					if (System.IO.File.Exists(file))
					{
						String basefile = System.IO.Path.GetFileName(file);
						String destfile = System.IO.Path.Combine(outDir, basefile);
						if (System.IO.File.Exists(destfile))
							System.IO.File.Delete(destfile);
						System.IO.File.Copy(file, destfile);
						qi.Files.Add(destfile);
						qi.ClientPaths.Add(file);
						qi.ChunkSize = resInfo.TransferChunkSize;
					}
				}

				lock (queue)
				{
					queue.Add(qi);
					SaveQueue();
				}
			}
			catch (Exception ex)
			{
				CommonLib.Tracer.ReportError("Failed to enqueue comm item", ex);
			}
			finally
			{
				CommonLib.Tracer.EndActivity("EnqueueMessage");
			}
		}
		/// <summary>
		/// Tells the result dispatcher that the queue result successfully reached the server.
		/// </summary>
		/// <param name="batchId"></param>
		public void AcceptMessage(Guid batchId)
		{
			CommonLib.Tracer.StartActivity("Accept Message");
			lock (queue)
			{
				ResultQueueItem foundItem = null;
				foreach (ResultQueueItem rqi in queue)
				{
					if (rqi.BatchId.Equals(batchId))
					{
						foundItem = rqi;
						break;
					}
				}
				if (foundItem != null)
				{
					// then let's delete everything
					String path = System.IO.Path.Combine(ClientNodeSettings.Default.CommQueueCopyDir, foundItem.BatchId.ToString());
					if (System.IO.Directory.Exists(path))
						System.IO.Directory.Delete(path, true);
					CommonLib.Tracer.ReportInformation("Removing " + batchId);
					queue.Remove(foundItem);
				}
				else
				{
					CommonLib.Tracer.ReportInformation("Item " + batchId + " could not be found");
				}
			}

			CommonLib.Tracer.EndActivity("Accept Message");
		}

		/// <summary>
		/// Clears all of the "IsSent" flags of the queue
		/// </summary>
		public void ResetQueue()
		{
			CommonLib.Tracer.ReportInformation("Reseting the queue");
			lock (queue)
			{
				foreach (ResultQueueItem rqi in queue)
				{
					rqi.IsSent = false;
				}
			}
		}

		public void ResetItem(Guid batchId)
		{
			CommonLib.Tracer.ReportInformation("Resetting item " + batchId);
			lock (queue)
			{
				foreach (ResultQueueItem rqi in queue)
				{
					if (rqi.BatchId.Equals(batchId))
					{
						rqi.IsSent = false;
						return;
					}
				}
			}
		}
	}
}